/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {ArkFile, ts} from "arkanalyzer";
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from "../BaseChecker";
import {Defects} from "../../Index";
import {FileMatcher, MatcherCallback, MatcherTypes} from "../../Index";
import {Rule} from "../../Index";
import {RuleListUtil} from "../../utils/common/DefectsList";
import {IssueReport} from "../../model/Defects";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoRedeclareCheck');

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-redeclare-check.md",
    description: "Disallow variable redeclaration."
};

type Options = [{
    builtinGlobals: boolean,
    ignoreDeclarationMerge: boolean
}];

enum CheckType {
    interface = 0,
    namespace = 1,
    class = 2,
    function = 3,
    enum = 4,
    typeAlias = 5,
    variable = 6,
    param
}

type CheckMember = {
    name: string;
    node: ts.Node,
    nameNode: ts.Node,
    forParentNode: ts.Node,
    type: CheckType
};

type GlobalMember = {
    node: ts.Node,
    sourceFile: ts.SourceFile,
    position: ts.LineAndCharacter,
    contentIndex: number
};

export class NoRedeclareCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private readonly defaultTypes = ['Array', 'Object', 'Function', 'Boolean', 'String', 'Number', 'Symbol', 'Date', 'RegExp', 'Promise', 'Proxy', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'eval', 'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'Infinity', 'NaN', 'undefined', 'JSON', 'Math', 'Intl', 'Atomics', 'Reflect', 'WebAssembly', 'BigInt', 'globalThis'];
    private builtinGlobals = true;
    private ignoreDeclarationMerge = true;

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatchBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [fileMatchBuildCb];
    }

    public check = (targetFile: ArkFile) => {
        if (this.rule && this.rule.option) {
            const option = this.rule.option as Options;
            if (option.length > 0) {
                this.builtinGlobals = option[0].builtinGlobals;
                this.ignoreDeclarationMerge = option[0].ignoreDeclarationMerge;
            }
        }

        const sourceFile = ts.createSourceFile('temp.ts', targetFile.getCode(), ts.ScriptTarget.Latest, true);
        const sourceFileObject = ts.getParseTreeNode(sourceFile);
        if (sourceFileObject == undefined) {
            return;
        }

        this.checkGloabels(sourceFile, targetFile);

        this.loopNode(targetFile, sourceFile, sourceFileObject);
    }

    public loopNode(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: ts.Node) {
        const children = aNode.getChildren();
        for (const child of children) {
            if (child.kind == ts.SyntaxKind.SyntaxList) {
                this.checkSyntaxListRedeclare(targetFile, sourceFile, child);
            } else if (child.kind == ts.SyntaxKind.Block) {
                this.checkBlockRedeclare(targetFile, sourceFile, child);
            } else {
                const memberList = this.objectNeedCheck(child, child);
                for (const member of memberList) {
                    this.checkRedeclare(targetFile, sourceFile, member);
                }
            }

            this.loopNode(targetFile, sourceFile, child);
        }
    }

    // 检测参数和代码块内的变量是否重定义，例如 function A(p) { var p; }
    private checkBlockRedeclare(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: ts.Node) {
        const parent = aNode.parent;
        if (!parent) {
            return;
        }

        if (ts.isFunctionDeclaration(parent)) {
            const paramList: CheckMember[] = [];
            for (const parameter of parent.parameters) {
                const obj = { name: parameter.name.getText(), nameNode: parameter, node: parameter, forParentNode: aNode, type: CheckType.variable };
                paramList.push(obj);
            }

            if (paramList.length == 0) {
                return;
            }

            const syntaxNodeList = this.objectNeedCheck(aNode, aNode);
            for (const node of syntaxNodeList) {
                this.checkRedeclareInMembers(targetFile, sourceFile, node, paramList);
            }
        } else {
            const memberList = this.objectNeedCheck(aNode, aNode.parent);
            for (const member of memberList) {
                this.checkRedeclare(targetFile, sourceFile, member);
            }
        }
    }

    private checkSyntaxListRedeclare(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: ts.Node) {
        const memberList = this.getSyntaxListNodes(aNode, aNode);
        for (const checkMember of memberList) {
            this.checkRedeclareInMembers(targetFile, sourceFile, checkMember, memberList)
        }
    }

    private getSpecifyChild(aNode: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
        for (const child of aNode.getChildren()) {
            if (child.kind == kind) {
                return child;
            }
            let result = this.getSpecifyChild(child, kind);
            if (result) {
                return result;
            }
        }
    }

    private checkRedeclare(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: CheckMember) {
        let parent = aNode.forParentNode.parent;
        if (!parent) {
            return;
        }

        if (ts.isBlock(parent)) {
            parent = parent.getChildren()[1];
        }
        let children = parent.getChildren();
        if (children.length == 0) {
            return;
        }

        if (parent.kind == ts.SyntaxKind.SourceFile) {
            children = children[0].getChildren();
        }

        this.checkRedeclareInNodes(targetFile, sourceFile, aNode, children);
    }

    private checkRedeclareInMembers(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: CheckMember, memberList: CheckMember[]) {
        let nodeList: ts.Node[] = [];
        for (const checkMember of memberList) {
            nodeList.push(checkMember.node);
        }
        this.checkRedeclareInNodes(targetFile, sourceFile, aNode, nodeList);
    }

    private checkBuiltIn(targetFile: ArkFile, sourceFile: ts.SourceFileLike, name: string, startLine: number, startCol: number): boolean {
        if (this.isBuiltinKeyword(name)) {
            const message = "'" + name + "' is already defined as a built-in global variable.";
            this.addIssueReport(targetFile, startLine, startCol, 0, message);
            return true;
        }
        return false;
    }

    private checkRedeclareInNodes(targetFile: ArkFile, sourceFile: ts.SourceFileLike, aNode: CheckMember, nodeList: ts.Node[]) {
        const originStartPosition = ts.getLineAndCharacterOfPosition(sourceFile, aNode.nameNode.getStart());
        const startLine = originStartPosition.line + 1;
        const startCol = originStartPosition.character + 1;
        if (this.checkBuiltIn(targetFile, sourceFile, aNode.name, startLine, startCol)) {
            return;
        }

        for (const child of nodeList) {
            const memberList = this.objectNeedCheck(child, aNode.forParentNode);
            for (const member of memberList) {
                if (member.node.pos == aNode.node.pos && member.node.kind == aNode.node.kind) {
                    continue;
                }

                if (member.name != aNode.name) {
                    continue;
                }

                const targetStartPosition = ts.getLineAndCharacterOfPosition(sourceFile, member.nameNode.getStart());
                if (targetStartPosition.line > originStartPosition.line ||
                    (targetStartPosition.line == originStartPosition.line && targetStartPosition.character > originStartPosition.character)) {
                    continue;
                }

                if (this.isNodeChecked(sourceFile, member.nameNode)) {
                    continue;
                }

                if (!this.needCheckIgnoreDeclarationMerge(aNode, member)) {
                    continue;
                }

                const message = "'" + aNode.name + "' is already defined.";
                const endPosition = ts.getLineAndCharacterOfPosition(sourceFile, aNode.nameNode.getEnd());
                const endCol = endPosition.character + 1;
                this.addIssueReport(targetFile, startLine, startCol, endCol, message);
            }
        }
    }

    // 要检测的节点是否已经在错误列表中
    private isNodeChecked(sourceFile: ts.SourceFileLike, aNode: ts.Node): boolean {
        const startPosition = ts.getLineAndCharacterOfPosition(sourceFile, aNode.getStart());
        const startLine = startPosition.line + 1;
        const startCol = startPosition.character + 1;
        for (const issue of this.issues) {
            if (issue.defect.reportLine == startLine && issue.defect.reportColumn == startCol) {
                return true;
            }
        }
        return false;
    }

    private objectNeedCheck(node: ts.Node, forParentNode: ts.Node): CheckMember[] {
        if (ts.isModuleDeclaration(node)) {
            const name = node.name?.text;
            if (name) {
                return [{ name: name, node: node, nameNode: node.name, forParentNode: forParentNode, type: CheckType.namespace}];
            }
        } else if (ts.isClassDeclaration(node)) {
            const name = node.name?.text;
            if (name) {
                return [{ name: name, node: node, nameNode: node.name, forParentNode: forParentNode, type: CheckType.class}];
            }
        } else if (ts.isInterfaceDeclaration(node)) {
            const name = node.name?.text;
            if (name) {
                return [{ name: name, node: node, nameNode: node.name, forParentNode: forParentNode, type: CheckType.interface}];
            }
        } else if (ts.isTypeAliasDeclaration(node)) {
            const name = node.name?.text;
            if (name) {
                return [{ name: name, node: node, nameNode: node.name, forParentNode: forParentNode, type: CheckType.typeAlias}];
            }
        } else if (ts.isVariableStatement(node)) {
            let declarationList = node.declarationList.declarations;
            if (declarationList.length > 0) {
                for (const variableDeclarator of declarationList) {
                    let varName = variableDeclarator.name;
                    if (ts.isIdentifier(varName)) {
                        return [{ name: varName.text, nameNode: varName, node: node, forParentNode: forParentNode, type: CheckType.variable }];
                    } else if (ts.isObjectBindingPattern(varName)) {
                        let members: CheckMember[] = [];
                        for (const element of varName.elements) {
                            const obj = { name: element.name.getText(), nameNode: element, node: node, forParentNode: forParentNode, type: CheckType.variable };
                            members.push(obj);
                        }
                        return members;
                    }
                }
            }
        } else if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
            if (node.name?.text) {
                return [{ name: node.name?.text, nameNode: node.name, node: node, forParentNode: forParentNode, type: CheckType.function }];
            }
        } else if (ts.isEnumDeclaration(node)) {
            if (node.name?.text) {
                return [{ name: node.name?.text, nameNode: node.name, node: node, forParentNode: forParentNode, type: CheckType.enum }];
            }
        } else if (ts.isSwitchStatement(node)) {
            return this.getSwitchCaseNodes(node, forParentNode);
        } else if (ts.isIfStatement(node)) {
            return this.getIfNodes(node, forParentNode);
        } else if (ts.isBlock(node)) {
            if (node.getChildren().length == 3) {
                const syntaxNode = node.getChildren()[1];
                return this.getSyntaxListNodes(syntaxNode, forParentNode);
            }
        } else if (ts.isParameter(node)) {
            return [{ name: node.name.getText(), nameNode: node.name, node: node, forParentNode: forParentNode, type: CheckType.param }];
        } else if (ts.isVariableDeclarationList(node)) {
            let members: CheckMember[] = []
            for (const declaration of node.declarations) {
                declaration.name.getText()
                const obj = { name: declaration.name.getText(), nameNode: declaration.name, node: declaration, forParentNode: forParentNode, type: CheckType.variable };
                members.push(obj);
            }
            return members;
        } else if (ts.isForStatement(node)) {
            for (const child of node.getChildren()) {
                if (ts.isVariableDeclarationList(child)) {
                    return this.objectNeedCheck(child, forParentNode);
                }
            }
        }
        return [];
    }

    private getIfNodes(aNode: ts.IfStatement, forParentNode: ts.Node): CheckMember[] {
        return this.getIfNodesImplement(aNode, forParentNode);
    }

    private getIfNodesImplement(aNode: ts.IfStatement, forParentNode: ts.Node): CheckMember[] {
        let members: CheckMember[] = []
        const thenStatement = aNode.thenStatement;
        if (ts.isBlock(thenStatement)) {
            if (thenStatement.getChildren().length == 3) {
                const syntaxNode = thenStatement.getChildren()[1];
                members = members.concat(this.getSyntaxListNodes(syntaxNode, forParentNode));
            }
        } else {
            members = members.concat(this.objectNeedCheck(thenStatement, forParentNode));
        }

        const elseStatement = aNode.elseStatement;
        if (elseStatement) {
            if (ts.isIfStatement(elseStatement)) {
                members = members.concat(this.getIfNodesImplement(elseStatement, forParentNode));
            } else if (ts.isBlock(elseStatement)) {
                if (thenStatement.getChildren().length == 3) {
                    const syntaxNode = thenStatement.getChildren()[1];
                    members = members.concat(this.getSyntaxListNodes(syntaxNode, forParentNode));
                }
            } else {
                members = members.concat(this.objectNeedCheck(elseStatement, forParentNode));
            }
        }
        return members;
    }

    private getSwitchCaseNodes(aNode: ts.Node, forParentNode: ts.Node): CheckMember[] {
        const caseNode = this.getSpecifyChild(aNode, ts.SyntaxKind.CaseBlock);
        if (!caseNode) {
            return [];
        }

        const syntaxNode = this.getSpecifyChild(caseNode, ts.SyntaxKind.SyntaxList);
        if (!syntaxNode) {
            return [];
        }

        let memberList: CheckMember[] = [];
        for (const child of syntaxNode.getChildren()) {
            if (ts.isCaseOrDefaultClause(child)) {
                for (const childElement of child.statements) {
                    const member = this.objectNeedCheck(childElement, forParentNode);
                    memberList = memberList.concat(member);
                }
            }
        }

        return memberList;
    }

    private getSyntaxListNodes(aNode: ts.Node, forParentNode: ts.Node): CheckMember[] {
        let memberList: CheckMember[] = [];
        for (const child of aNode.getChildren()) {
            if (ts.isVariableStatement(child)) {
                const member = this.objectNeedCheck(child, forParentNode);
                memberList = memberList.concat(member);
            }
        }
        return memberList;
    }

    private isBuiltinKeyword(keyword: string): boolean {
        if (this.builtinGlobals) {
            if (this.defaultTypes.includes(keyword)) {
                return true;
            }
        }
        return false;
    }

    private needCheckIgnoreDeclarationMerge(firstNode: CheckMember, secondNode: CheckMember): boolean {
        if (this.ignoreDeclarationMerge) {
            if ((firstNode.type == CheckType.interface && secondNode.type == CheckType.interface) ||
                (firstNode.type == CheckType.namespace && secondNode.type == CheckType.namespace) ||
                (firstNode.type == CheckType.class && secondNode.type == CheckType.interface) ||
                (firstNode.type == CheckType.interface && secondNode.type == CheckType.class) ||
                (firstNode.type == CheckType.class && secondNode.type == CheckType.namespace) ||
                (firstNode.type == CheckType.namespace && secondNode.type == CheckType.class) ||
                (firstNode.type == CheckType.interface && secondNode.type == CheckType.namespace) ||
                (firstNode.type == CheckType.namespace && secondNode.type == CheckType.interface) ||
                (firstNode.type == CheckType.enum && secondNode.type == CheckType.namespace) ||
                (firstNode.type == CheckType.namespace && secondNode.type == CheckType.enum))
            {
                return false;
            }
        }
        return true;
    }

    private checkGloabels(sourceFile: ts.SourceFile, targetFile: ArkFile) {
        let commentsListList: GlobalMember[][] = [];
        const comments = this.getComments(sourceFile, targetFile.getFilePath());
        for (const comment of comments) {
            let commentsList: GlobalMember[] = [];
            let position = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos);

            const commentText = sourceFile.getFullText().substring(comment.pos, comment.end);
            if (comment.kind != ts.SyntaxKind.MultiLineCommentTrivia) {
                continue;
            }

            if (commentText.length < 5) {
                continue;
            }

            let content = commentText.substring(2, commentText.length - 2);

            const globalKeyword = "global";
            const globalsKeyword = "globals";
            if (!content.startsWith(" " + globalKeyword) && !content.startsWith("\r\n" + globalKeyword) &&
                !content.startsWith(" " + globalsKeyword) && !content.startsWith("\r\n" + globalsKeyword) &&
                !content.startsWith(globalKeyword) && !content.startsWith("\r\n" + globalKeyword) &&
                !content.startsWith(globalsKeyword) && !content.startsWith("\r\n" + globalsKeyword)) {
                continue;
            }

            // 找出前面空格数
            let spaceCount = 0;
            for (const contentKey of content) {
                if (contentKey == ' ') {
                    spaceCount = spaceCount + 1;
                } else if (contentKey == '\r\n') {
                    spaceCount = spaceCount + 4;
                } else {
                    break;
                }
            }
            content = content.substring(spaceCount);

            const keywordList = [globalKeyword, globalsKeyword];

            const contentIndex = 2 + spaceCount;
            const aSourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
            const sourceFileObject = ts.getParseTreeNode(aSourceFile);
            if (sourceFileObject == undefined) {
                continue;
            }

            let children = sourceFileObject.getChildren();
            if (children.length != 2) {
                continue;
            }

            let previousGlobals = false;
            children = children[0].getChildren();
            for (let idx = 0; idx < children.length; idx++) {
                const child = children[idx];
                if (keywordList.includes(child.getText())) {
                    previousGlobals = true;
                    continue;
                }

                if (ts.isLabeledStatement(child)) {
                    const element = child.label;

                    const pos = this.getNodePosition(sourceFile, child, position.line, position.character + contentIndex);
                    if (this.checkBuiltIn(targetFile, aSourceFile, child.label.getText(), pos.line, pos.character)) {
                        continue;
                    }

                    if (previousGlobals) {
                        commentsList.push({sourceFile: aSourceFile, node: element, position: position, contentIndex: contentIndex});
                    }
                }

                if (!ts.isExpressionStatement(child)) {
                    continue;
                }

                const nodeList = child.getChildren();
                for (const nodeElement of nodeList) {
                    let inPreviousGlobals = false;
                    if (ts.isBinaryExpression(nodeElement)) {
                        const nodeElementChildren = this.getBinaryExpressionChildren(nodeElement);
                        for (let i = 0; i < nodeElementChildren.length; i++) {
                            const element = nodeElementChildren[i];
                            const pos = this.getNodePosition(aSourceFile, element, position.line, position.character + contentIndex);
                            if (this.checkBuiltIn(targetFile, sourceFile, element.getText(), pos.line, pos.character)) {
                                continue;
                            }

                            if (ts.isIdentifier(element) && (previousGlobals && i == 0)) {
                                commentsList.push({sourceFile: aSourceFile, node: element, position: position, contentIndex: contentIndex});
                                previousGlobals = false;
                                inPreviousGlobals = false;
                            }

                            inPreviousGlobals = ts.isIdentifier(element) && keywordList.includes(element.getText());
                        }
                    } else if (ts.isIdentifier(nodeElement)) {
                        if (keywordList.includes(nodeElement.getText())) {
                            previousGlobals = true;
                        } else {
                            const pos = this.getNodePosition(aSourceFile, nodeElement, position.line, position.character + contentIndex);
                            if (this.checkBuiltIn(targetFile, sourceFile, nodeElement.getText(), pos.line, pos.character)) {
                                continue;
                            }

                            if (idx == 1 && previousGlobals) {
                                commentsList.push({sourceFile: aSourceFile, node: nodeElement, position: position, contentIndex: contentIndex});
                                previousGlobals = false;
                                inPreviousGlobals = false;
                            }
                        }
                    }

                    previousGlobals = inPreviousGlobals || (ts.isIdentifier(nodeElement) && keywordList.includes(nodeElement.getText()));
                }
            }

            if (commentsList.length > 0) {
                commentsListList.push(commentsList);
            }

        }

        for (let i = 0; i < commentsListList.length; i++) {
            const list = commentsListList[i];
            for (let j = 0; j < list.length; j++) {
                const sourceNode = list[j];
                for (let m = 0; m < commentsListList.length; m++) {
                    const listObject = commentsListList[m];
                    if (m == i) {
                        continue;
                    }
                    for (let n = 0; n < listObject.length; n++) {
                        const nodeObject = listObject[n];
                        if (nodeObject.node.getText() != sourceNode.node.getText()) {
                            continue;
                        }

                        const sourceLine = sourceNode.position.line;
                        const sourceCol = sourceNode.position.character + nodeObject.contentIndex;

                        const line = nodeObject.position.line;
                        const col = nodeObject.position.character + nodeObject.contentIndex;

                        if (sourceLine < line || (sourceLine == line && sourceCol < col)) {
                            this.addGlobalNode(nodeObject.sourceFile, targetFile, nodeObject.node, line, col);
                        }
                    }
                }
            }
        }
    }

    private getBinaryExpressionChildren(aNode: ts.Node): ts.Node[] {
        let children: ts.Node[] = [];
        const nodeList = aNode.getChildren();
        for (const element of nodeList) {
            if (ts.isBinaryExpression(element)) {
                children = children.concat(this.getBinaryExpressionChildren(element));
            } else {
                if (ts.isIdentifier(element)) {
                    children.push(element);
                }
            }
        }
        return children;
    }

    private getNodePosition(sourceFile: ts.SourceFileLike, aNode: ts.Node, line: number, col: number): ts.LineAndCharacter {
        const originStartPosition = ts.getLineAndCharacterOfPosition(sourceFile, aNode.getStart());
        let startLine = originStartPosition.line + line;
        let startCol = 0;
        if (originStartPosition.line > 0) {
            startCol = originStartPosition.character;
        } else {
            startCol = originStartPosition.character + col;
        }
        startLine = startLine + 1;
        startCol = startCol + 1;
        return {line: startLine, character: startCol};
    }

    private addGlobalNode(sourceFile: ts.SourceFileLike, targetFile: ArkFile, aNode: ts.Node, line: number, col: number) {
        const position = this.getNodePosition(sourceFile, aNode, line, col);
        const message = "'" + aNode.getText() + "' is already defined.";
        this.addIssueReport(targetFile, position.line, position.character, 0, message);
    }

    // 获取文件中所有注释（包括前导、尾随和未附着的注释）
    private getComments(sourceFile: ts.SourceFile, filePath: string): ts.CommentRange[] {
        const text = sourceFile.text;
        const commentRanges: ts.CommentRange[] = [];
        const seenComments = new Set<string>(); // 用于去重

        const visitNode = (node: ts.Node) => {
            const processCommentRanges = (ranges: ts.CommentRange[] | undefined) => {
                if (!ranges) return;
                ranges.forEach((commentRange) => {
                    const key = `${commentRange.pos}-${commentRange.end}`;
                    if (!seenComments.has(key)) {
                        commentRanges.push(commentRange);
                        seenComments.add(key);
                    }
                });
            };

            const leadingComments = ts.getLeadingCommentRanges(text, node.pos);
            processCommentRanges(leadingComments);

            const trailingComments = ts.getTrailingCommentRanges(text, node.end);
            processCommentRanges(trailingComments);

            ts.forEachChild(node, visitNode);
        };

        visitNode(sourceFile);
        return commentRanges;
    }

    private addIssueReport(arkFile: ArkFile, line: number, startCol: number, endCol: number, message: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const defect = new Defects(line, startCol, endCol, message, severity, this.rule.ruleId,
            filePath, this.metaData.ruleDocPath, true, false, false);

        for (const issue of this.issues) {
            if (issue.defect.reportLine == line && issue.defect.reportColumn == startCol) {
                return;
            }
        }

        this.issues.push(new IssueReport(defect, undefined));

        // 对 issues 进行排序
        this.issues.sort((a, b) => {
            if (a.defect.reportLine === b.defect.reportLine) {
                return a.defect.reportColumn - b.defect.reportColumn;
            }
            return a.defect.reportLine - b.defect.reportLine;
        });

        RuleListUtil.push(defect);
    }
}